/*
 * $Id: sio.c,v 1.30 2006-01-15 06:51:35 bacon Exp $
 */

#include <xp/bas/sio.h>
#include <xp/bas/memory.h>
#include <xp/bas/errno.h>
#include <xp/bas/assert.h>

#define STATUS_FIO_ACTIVE (1 << 0)
#define STATUS_TIO_ACTIVE (1 << 1)

static xp_ssize_t __sio_input (int cmd, void* arg, void* buf, xp_size_t size);
static xp_ssize_t __sio_output (int cmd, void* arg, void* buf, xp_size_t size);

static xp_sio_t __sio_in = 
{
	xp_false, /* __dynamic */
	STATUS_FIO_ACTIVE | STATUS_TIO_ACTIVE, /* __status */	

	/* fio */
	{
		xp_false, 
#ifdef _WIN32
		(HANDLE)STD_INPUT_HANDLE,
		0
#else
		0
#endif
	},

	/* tio */
	{
		xp_false,
		0,

		__sio_input,
		__sio_output,
		&__sio_in,	
		&__sio_in,

		0,
		0,
		0,
		0
	}
};

static xp_sio_t __sio_out = 
{
	xp_false, /* __dynamic */
	STATUS_FIO_ACTIVE | STATUS_TIO_ACTIVE, /* __status */	

	/* fio */
	{
		xp_false,
#ifdef _WIN32
		(HANDLE)STD_OUTPUT_HANDLE,
		0
#else
		1
#endif
	},

	/* tio */
	{
		xp_false,
		0,

		__sio_input,
		__sio_output,
		&__sio_out,	
		&__sio_out,

		0,
		0,
		0,
		0
	}
};

static xp_sio_t __sio_err = 
{
	xp_false, /* __dynamic */
	STATUS_FIO_ACTIVE | STATUS_TIO_ACTIVE, /* __status */	

	{
		xp_false,
#ifdef _WIN32
		(HANDLE)STD_ERROR_HANDLE,
		0
#else
		2
#endif
	},

	/* tio */
	{
		xp_false,
		0,

		__sio_input,
		__sio_output,
		&__sio_err,	
		&__sio_err,

		0,
		0,
		0,
		0
	}
};

xp_sio_t* xp_sio_in = &__sio_in;
xp_sio_t* xp_sio_out = &__sio_out;
xp_sio_t* xp_sio_err = &__sio_err;

xp_sio_t* xp_sio_open (
	xp_sio_t* sio, const xp_char_t* file, const xp_char_t* mode)
{
	int plus, flags;

	if (file == XP_NULL || file[0] == XP_CHAR('\0')) return XP_NULL;

	plus = (mode[1] == XP_CHAR('+'));

	if (mode[0] == XP_CHAR('r')) {
		flags = XP_FIO_READ;
		if (plus) flags |= XP_FIO_WRITE;
	}
	else if (mode[0] == XP_CHAR('w')) {
		flags = XP_FIO_WRITE;
		if (plus) flags |= XP_FIO_READ;
		flags |= XP_FIO_TRUNCATE | XP_FIO_CREATE;
	}
	else if (mode[0] == XP_CHAR('a')) {
		flags = XP_FIO_WRITE;
		if (plus) flags |= XP_FIO_READ;
		flags |= XP_FIO_APPEND | XP_FIO_CREATE;
	}
	else {
		xp_seterrno (XP_EINVAL);
		return XP_NULL;
	}

	if (sio == XP_NULL) {
		sio = (xp_sio_t*)xp_malloc(xp_sizeof(xp_sio_t));
		if (sio == XP_NULL) return XP_NULL;
		sio->__dynamic = xp_true;
	}
	else sio->__dynamic = xp_false;

	if (xp_fio_open(&sio->fio, file, flags, 0644) == XP_NULL) {
		if (sio->__dynamic) xp_free (sio);
		return XP_NULL;
	}

	if (xp_tio_open(&sio->tio) == XP_NULL) {
		xp_fio_close (&sio->fio);
		if (sio->__dynamic) xp_free (sio);
		return XP_NULL;
	}

	if (xp_tio_attinp(&sio->tio, __sio_input, sio) == -1 ||
	    xp_tio_attoutp(&sio->tio, __sio_output, sio) == -1) {
		xp_tio_close (&sio->tio);	
		xp_fio_close (&sio->fio);
		if (sio->__dynamic) xp_free (sio);
		return XP_NULL;
	}

	sio->__status = STATUS_FIO_ACTIVE | STATUS_TIO_ACTIVE;
	return sio;
}

xp_sio_t* xp_sio_openhnd (xp_sio_t* sio, xp_fiohnd_t handle)
{
	if (sio == XP_NULL) {
		sio = (xp_sio_t*)xp_malloc(xp_sizeof(xp_sio_t));
		if (sio == XP_NULL) return XP_NULL;
		sio->__dynamic = xp_true;
	}
	else sio->__dynamic = xp_false;

	if (xp_fio_openhnd(&sio->fio, handle) == XP_NULL) {
		if (sio->__dynamic) xp_free (sio);
		return XP_NULL;
	}

	if (xp_tio_open(&sio->tio) == XP_NULL) {
		xp_fio_forfeit (&sio->fio);
		if (sio->__dynamic) xp_free (sio);
		return XP_NULL;
	}

	if (xp_tio_attinp(&sio->tio, __sio_input, sio) == -1 ||
	    xp_tio_attoutp(&sio->tio, __sio_output, sio) == -1) {
		xp_tio_close (&sio->tio);	
		xp_fio_forfeit (&sio->fio);
		if (sio->__dynamic) xp_free (sio);
		return XP_NULL;
	}

	sio->__status = STATUS_FIO_ACTIVE | STATUS_TIO_ACTIVE;
	return sio;
}

int xp_sio_close (xp_sio_t* sio)
{
	/*if (xp_sio_flush (sio) == -1) return -1;*/
	if ((sio->__status & STATUS_FIO_ACTIVE) && 
	    (sio->__status & STATUS_TIO_ACTIVE)) xp_sio_flush (sio);

	if (sio->__status & STATUS_TIO_ACTIVE) {
		if (xp_tio_close(&sio->tio) == -1) return -1;
		sio->__status &= ~STATUS_TIO_ACTIVE;
	}
	if (sio->__status & STATUS_FIO_ACTIVE) {
		if (xp_fio_close(&sio->fio) == -1) return -1;
		sio->__status &= ~STATUS_FIO_ACTIVE;
	}

	if (sio->__dynamic) xp_free (sio);

	if (sio == xp_sio_in) {
		xp_sio_in = XP_NULL;
		return 0;
	}
	if (sio == xp_sio_out) {
		xp_sio_out = XP_NULL;
		return 0;
	}
	if (sio == xp_sio_err) {
		xp_sio_err = XP_NULL;
		return 0;
	}

	return 0;
}

xp_fiohnd_t xp_sio_gethnd (xp_sio_t* sio)
{
	return sio->fio.handle;
}

xp_ssize_t xp_sio_flush (xp_sio_t* sio)
{
	return xp_tio_flush (&sio->tio);
}

void xp_sio_purge (xp_sio_t* sio)
{
	xp_tio_purge (&sio->tio);
}

xp_ssize_t xp_sio_getc (xp_sio_t* sio, xp_char_t* c)
{
	return xp_tio_getc (&sio->tio, c);
}

xp_ssize_t xp_sio_gets (xp_sio_t* sio, xp_char_t* buf, xp_size_t size)
{
	return xp_tio_gets (&sio->tio, buf, size);
}

xp_ssize_t xp_sio_getsx (xp_sio_t* sio, xp_char_t* buf, xp_size_t size)
{
	return xp_tio_getsx (&sio->tio, buf, size);
}

xp_ssize_t xp_sio_getstr (xp_sio_t* sio, xp_str_t* buf)
{
	return xp_tio_getstr (&sio->tio, buf);
}

xp_ssize_t xp_sio_putc (xp_sio_t* sio, xp_char_t c)
{
	return xp_tio_putc (&sio->tio, c);
}

xp_ssize_t xp_sio_puts (xp_sio_t* sio, const xp_char_t* str)
{
	return xp_tio_puts (&sio->tio, str);
}

xp_ssize_t xp_sio_putsx (xp_sio_t* sio, const xp_char_t* str, xp_size_t size)
{
	return xp_tio_putsx (&sio->tio, str, size);
}

xp_ssize_t xp_sio_putsn (xp_sio_t* sio, ...)
{
	xp_ssize_t n;
	xp_va_list ap;

	xp_va_start (ap, sio);
	n = xp_tio_putsv (&sio->tio, ap);
	xp_va_end (ap);

	return n;
}

xp_ssize_t xp_sio_putsxn (xp_sio_t* sio, ...)
{
	xp_ssize_t n;
	xp_va_list ap;

	xp_va_start (ap, sio);
	n = xp_tio_putsxv (&sio->tio, ap);
	xp_va_end (ap);

	return n;
}

xp_ssize_t xp_sio_putsv (xp_sio_t* sio, xp_va_list ap)
{
	return xp_tio_putsv (&sio->tio, ap);
}

xp_ssize_t xp_sio_putsxv (xp_sio_t* sio, xp_va_list ap)
{
	return xp_tio_putsxv (&sio->tio, ap);
}

int xp_sio_getpos (xp_sio_t* sio, xp_siopos_t* pos)
{
	xp_off_t off;

	off = xp_fio_seek (&sio->fio, 0, XP_FIO_SEEK_CURRENT);
	if (off == (xp_off_t)-1) return -1;

	*pos = off;
	return 0;
}

int xp_sio_setpos (xp_sio_t* sio, xp_siopos_t pos)
{
	if (xp_sio_flush(sio) == -1) return -1;
	return (xp_fio_seek (&sio->fio,
		pos, XP_FIO_SEEK_BEGIN) == (xp_off_t)-1)? -1: 0;
}

int xp_sio_rewind (xp_sio_t* sio)
{
	if (xp_sio_flush(sio) == -1) return -1;
	return (xp_fio_seek (&sio->fio, 
		0, XP_FIO_SEEK_BEGIN) == (xp_off_t)-1)? -1: 0;
}

int xp_sio_movetoend (xp_sio_t* sio)
{
	if (xp_sio_flush(sio) == -1) return -1;
	return (xp_fio_seek (&sio->fio, 
		0, XP_FIO_SEEK_END) == (xp_off_t)-1)? -1: 0;
}

static xp_ssize_t __sio_input (int cmd, void* arg, void* buf, xp_size_t size)
{
	xp_sio_t* sio = (xp_sio_t*)arg;

	xp_assert (sio != XP_NULL);
	if (cmd == XP_TIO_IO_DATA) {
		xp_assert (sio->__status & STATUS_FIO_ACTIVE);
		return xp_fio_read (&sio->fio, buf, size);
	}

	return 0;
}

static xp_ssize_t __sio_output (int cmd, void* arg, void* buf, xp_size_t size)
{
	xp_sio_t* sio = (xp_sio_t*)arg;

	xp_assert (sio != XP_NULL);
	if (cmd == XP_TIO_IO_DATA) {
		xp_assert (sio->__status & STATUS_FIO_ACTIVE);
		return xp_fio_write (&sio->fio, buf, size);
	}

	return 0;
}

